!DOCTYPE html> Structure de données - Mon Projet : KARPLUS STRONG

L'algorithme Karplus-Strong : la musique dans le numérique

Le problème initial

La musique : cet art qui existe depuis si longtemps, et qui consiste à enchaîner des sons pour façonner quelque chose de beau. Cette pratique a évolué au cours du temps, avec les changements d'instruments ou encore les changements des goûts. Et ainsi, aujourd'hui, la musique doit s'adapter à la tendance actuelle qu'est le numérique.

C'est là que réside le problème : comment numériser un son ? C'est le principe que l'algorithme de Karplus-Strong cherche à résoudre.

L'algorithme

Le principe

L'algorithme de Karplus-Strong produit des échantillons de son très rapprochés : plus les échantillons sont rapprochés, plus le son numérique est fidèle au son naturel. Ainsi, l'algorithme doit suivre la règle suivante : pour que le son corresponde, il faut que la fréquence d'échantillonnage soit au minimum le double de la fréquence du son naturel. Ensuite, on fait la moyenne des deux premiers échantillons, puis on met cette moyenne à la fin du "buffer" (tableau d'échantillons du son), puis on enlève le premier échantillon, et on recommence jusqu'à obtenir un son potable, soit jusqu'à ce qu'on aie fait autant de boucles que la fréquence d'échantillonnage multiplié par le temps souhaité.

Caramba ! Encore raté !
Graphique décrivant le problème présenté

Le codage de l'algorithme

La structure de données utilisé

« Les mauvais programmeurs s'inquiètent du code. Les bons programmeurs s'inquiètent des structures de données et de leurs relations. » - Linus Torvald

En effet, il est important de réfléchir à la structure de données que l'on utilise pour son code, particulièrement dans les cas où l'on travaille avec un très grand nombre d'éléments. Et ici, on utilise des échantillons très rapprochés dans le temps, qui atteignent plusieurs dizaines de milliers ! Nous allons donc décider de la structure de données optimale ci-dessous.

Structure de données Ajout d'élément en dernière place Accès à la première valeur Accès à la deuxième valeur
Liste chainée O(n) O(1) O(1)
Pile X O(1) O(1)
File O(1) O(1) O(1)

Notre gagnant est donc la file !

Le code

Dans le code ci-dessous, nous allons considérer un tableau de nombres aléatoires comme le son de travail.


###Importations#######################
from audio import *
from TAD_file_dyna import *
from random import randint

###Fonctions#########################

def moyenne(val1, val2):
    '''
    moyenne:
    fonction qui fait la moyenne pondérée de deux valeurs
    :param: val1 et val2 : int
    :return: int moyenne pondérée
    '''
    alpha=0.8
    beta=1-alpha
    return 1/(alpha+beta)*(alpha*val1+beta*val2)


def karplus_strong(note, f_ech, t):
    '''
    karplus_strong:
    fonction qui crée un tableau de valeurs entières aléatoires entre -32767 et 32767 et en fait la moyenne pondérée plusieurs fois pour renvoyer un tableau de fréquences approximées qui peuvent se retranscrire en son
    :param: note : str nom de la note qu'on recherche
        f_ech : int fréquence d'échantillons
        t : temps du tableau final
    :return: tab_son : tableau final de valeurs moyennées pour faire un son
    
    '''
    notes={'do3':262, 're3':294, 'mi3':330, 'fa3':349, 'sol3':392, 'la4': 440, 'si4': 494, 'do#3': 277, 'mib3': 311, 'fa#3': 370, 'sol#3': 415, 'sib4': 466, 'do4':523, 're4': 588}  #détermine la note dont on prend la fréquence
    tab_son=[]
    f=notes[note]
    buffer=FileDyna()
    for i in range(f_ech//f):
        buffer.enfiler(randint(-32767, 32767)) #Création des éléments aléatoires du buffer
    val1=buffer.defiler()  #On défile la valeur ici pour pouvoir lire deux valeurs consécutives de la file à la fois
    for i in range(t*f_ech):
        val2=buffer.defiler()
        m=int(moyenne(val1, val2))
        buffer.enfiler(m)
        tab_son.append(val1)
        val1=val2  #On récupère val2 pour lire la valeur suivante
    return tab_son

###Mélodies########################

def megalovania(f_ech):
    """
    Crée les premières notes de MEGALOVANIA de Toby Fox
    """
    t=1     #1 est une blanche en 60
    tab1=croche(karplus_strong('re3', f_ech, t))
    tab2=noire(karplus_strong('re4', f_ech, t))
    tab3=noire(karplus_strong('la4', f_ech, t))
    t_mega=tab1*2+tab2+tab3
    return t_mega

def surprise(f_ech):
    """
    Crée une surprise >:3
    """
    t=1
    do3=double(karplus_strong('do3', f_ech, t))
    re3=double(karplus_strong('re3', f_ech, t))
    fa3=double(karplus_strong('fa3', f_ech, t))
    la4p=croche_p(karplus_strong('la4', f_ech, t))
    sol3np=noire_p(karplus_strong('sol3', f_ech, t))
    sol3p=croche_p(karplus_strong('sol3', f_ech, t))
    fa3p=croche_p(karplus_strong('fa3', f_ech, t))
    mi3=double(karplus_strong('mi3', f_ech, t))
    re3c=croche(karplus_strong('re3', f_ech, t))
    fa3n=noire(karplus_strong('fa3', f_ech, t))
    sol3c=croche(karplus_strong('sol3', f_ech, t))
    mi3p=croche_p(karplus_strong('mi3', f_ech, t))
    do3n=noire(karplus_strong('do3', f_ech, t))
    do3c=croche(karplus_strong('do3', f_ech, t))
    sol3n=noire(karplus_strong('sol3', f_ech, t))
    fa3n=karplus_strong('fa3', f_ech, t)

    surprise=do3+re3+fa3+re3+la4p*2+sol3np+do3+re3+fa3+re3+sol3p*2+fa3p+mi3+re3c+do3+re3+fa3+re3+fa3n+sol3c+mi3p+re3+do3n+do3c
    return surprise

###Fonctions Rythmes##################

def noire(tab):
    tab_div=[]
    for i in range(len(tab)//2):
        tab_div.append(tab[i])
    return tab_div

def croche(tab):
    tab_div=[]
    for i in range(len(tab)//4):
        tab_div.append(tab[i])
    return tab_div

def double(tab):
    tab_div=[]
    for i in range(len(tab)//8):
        tab_div.append(tab[i])
    return tab_div

def croche_p(tab):
    tab_div=[]
    for i in range(len(tab)//5):
        tab_div.append(tab[i])
    return tab_div

def noire_p(tab):
    tab_div=[]
    for i in range(int(len(tab)/1.5)):
        tab_div.append(tab[i])
    return tab_div

###Programme Principal#######################

f_ech=20000
t=3


tab=karplus_strong('la4', f_ech, t)

mega=megalovania(f_ech)

surp=surprise(f_ech)

write_wav('La4.wav', tab, 20000)
write_wav('Megalovania', mega, f_ech)
write_wav('Surprise', surp, f_ech)



			

Allez-y, écoutez la surprise voir si elle marche ! 😏

Les limites de l'algorithme

Comme tout bon problème, celui-ci ne se résout pas aussi facilement : l'algorithme présente une limite importante.

Le compromis qualité-poids

En effet, la meilleure la qualité, le plus fréquent sont les échantillons, et ainsi, le plus lourd le fichier. Autant, si on choisit 20000 échantillons par seconde, le fichier ne pèse pas trop, mais il n'en est pas de même pour un choix de 1 000 000 000 échantillons. Il faut ainsi trouver un juste milieu.

Les rythmes (la longueur des notes)

On ne peut utiliser que des entiers comme temps : cela soulève le problème des rythmes courts, car on ne peut théoriquement pas descendre en-dessous d'une noire en ♩ =60, soit une note par seconde.

Cependant, on peut résoudre ce problème en divisant directement la taille du tableau son, en divisant de façon euclidienne par 2 pour une croche, ou 4 pour une double croche. Mais on se bute à un nouveau problème : on ne peut pas diviser par un float, car les temps ne prennent que des int, et ceci nous empêche de faire des notes pointées.

Caramba ! Encore raté !